/*******************************************************************************
* Copyright (c) 2007 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*    Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation
*******************************************************************************/

package org.eclipse.ceylon.ide.eclipse.code.editor;

import static org.eclipse.ceylon.ide.eclipse.code.editor.CeylonSourceViewerConfiguration.configCompletionPopup;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.ADD_BLOCK_COMMENT;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.CORRECT_INDENTATION;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.GOTO_MATCHING_FENCE;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.REMOVE_BLOCK_COMMENT;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.RESTORE_PREVIOUS;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.SELECT_ENCLOSING;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.SHOW_OUTLINE;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionIds.TOGGLE_COMMENT;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorInputUtils.getFile;
import static org.eclipse.ceylon.ide.eclipse.code.editor.EditorInputUtils.getPath;
import static org.eclipse.ceylon.ide.eclipse.code.editor.SourceArchiveDocumentProvider.isSrcArchive;
import static org.eclipse.ceylon.ide.eclipse.code.outline.CeylonLabelProvider.getImageForFile;
import static org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonPreferenceInitializer.CLEAN_IMPORTS;
import static org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonPreferenceInitializer.FORMAT;
import static org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonPreferenceInitializer.NORMALIZE_NL;
import static org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonPreferenceInitializer.NORMALIZE_WS;
import static org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonPreferenceInitializer.STRIP_TRAILING_WS;
import static org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonPreferenceInitializer.SUB_WORD_NAVIGATION;
import static org.eclipse.ceylon.ide.eclipse.java2ceylon.Java2CeylonProxies.editorJ2C;
import static org.eclipse.ceylon.ide.eclipse.java2ceylon.Java2CeylonProxies.importsJ2C;
import static org.eclipse.ceylon.ide.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C;
import static org.eclipse.ceylon.ide.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C;
import static org.eclipse.ceylon.ide.eclipse.ui.CeylonPlugin.PLUGIN_ID;
import static org.eclipse.ceylon.ide.eclipse.util.EditorUtil.getCurrentTheme;
import static org.eclipse.ceylon.ide.eclipse.util.Nodes.findNode;
import static java.util.ResourceBundle.getBundle;
import static org.eclipse.core.resources.IncrementalProjectBuilder.CLEAN_BUILD;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import static org.eclipse.jdt.ui.PreferenceConstants.EDITOR_FOLDING_ENABLED;
import static org.eclipse.jface.text.DocumentRewriteSessionType.SEQUENTIAL;
import static org.eclipse.ui.PlatformUI.getWorkbench;
import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS;
import static org.eclipse.ui.texteditor.ITextEditorActionConstants.GROUP_RULERS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_NEXT_WORD;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_NEXT;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_NEXT;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_PREVIOUS;

import java.lang.reflect.Method;
import java.text.BreakIterator;
import java.text.CharacterIterator;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.ui.actions.IRunToLineTarget;
import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget;
import org.eclipse.debug.ui.actions.ToggleBreakpointAction;
import org.eclipse.jdt.internal.debug.ui.actions.JavaBreakpointPropertiesRulerAction;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.IVerticalRulerInfo;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.StyledTextContent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.IEditorStatusLine;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.TextNavigationAction;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

import org.eclipse.ceylon.compiler.typechecker.tree.Node;
import org.eclipse.ceylon.ide.eclipse.code.outline.CeylonOutlinePage;
import org.eclipse.ceylon.ide.eclipse.code.outline.NavigateMenuItems;
import org.eclipse.ceylon.ide.eclipse.code.parse.CeylonParseController;
import org.eclipse.ceylon.ide.eclipse.code.parse.CeylonParserScheduler;
import org.eclipse.ceylon.ide.eclipse.code.parse.TreeLifecycleListener;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonCompletionPreferencePage;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonEditorPreferencePage;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonFiltersPreferencePage;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonOpenDialogsPreferencePage;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonOutlinesPreferencePage;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonRefactoringPreferencePage;
import org.eclipse.ceylon.ide.eclipse.code.preferences.CeylonSaveActionsPreferencePage;
import org.eclipse.ceylon.ide.eclipse.code.refactor.RefactorMenuItems;
import org.eclipse.ceylon.ide.eclipse.code.search.FindMenuItems;
import org.eclipse.ceylon.ide.eclipse.ui.CeylonPlugin;
import org.eclipse.ceylon.ide.eclipse.util.EditorUtil;
import org.eclipse.ceylon.ide.eclipse.util.Highlights;
import org.eclipse.ceylon.ide.common.model.CeylonProject;
import org.eclipse.ceylon.ide.common.model.CeylonProjectBuild;
import org.eclipse.ceylon.ide.common.model.ModelListener;
import org.eclipse.ceylon.ide.common.typechecker.ExternalPhasedUnit;
import org.eclipse.ceylon.ide.common.typechecker.ProjectPhasedUnit;
import org.eclipse.ceylon.ide.common.vfs.FileVirtualFile;

import ceylon.language.Iterable;

/**
 * An editor for Ceylon source code.
 * 
 * @author Gavin King
 * @author Chris Laffra
 * @author Robert M. Fuhrer
 */
public class CeylonEditor extends TextEditor implements ModelListener<IProject, IResource, IFolder, IFile> {
    
    private static final Pattern TRAILING_WS = 
            Pattern.compile("[ \\t]+$", Pattern.MULTILINE);

    public static final String MESSAGE_BUNDLE = 
            "org.eclipse.ceylon.ide.eclipse.code.editor.EditorActionMessages";

    private static final int REPARSE_SCHEDULE_DELAY = 200;

    private CeylonParserScheduler parserScheduler;
    private ProblemMarkerManager problemMarkerManager;
    private ICharacterPairMatcher bracketMatcher;
    private ToggleBreakpointAction toggleBreakpointAction;
    private IAction enableDisableBreakpointAction;
    private IAction breakpointPropertiesAction;
    private FoldingActionGroup foldingActionGroup;
    private SourceArchiveDocumentProvider sourceArchiveDocumentProvider;
    private ToggleBreakpointAdapter toggleBreakpointTarget;
    private CeylonOutlinePage outlinePage;
    private boolean backgroundParsingPaused;
    private CeylonParseController parseController;
    private ProjectionSupport projectionSupport;
    private LinkedModeModel linkedMode;
    private Object linkedModeOwner;
    
    private MarkerAnnotationUpdater markerAnnotationUpdater = 
            new MarkerAnnotationUpdater(this);
    private ProjectionAnnotationManager projectionAnnotationManager = 
            new ProjectionAnnotationManager(this);
    private AnnotationCreator annotationCreator = 
            new AnnotationCreator(this);
    private RefinementAnnotationCreator refinementAnnotationCreator = 
            new RefinementAnnotationCreator(this);
    private RangeAnnotationCreator rangeAnnotationCreator = 
            new RangeAnnotationCreator(this);
    
    ToggleFoldingRunner fFoldingRunner;
    
    public CeylonEditor() {
        setSourceViewerConfiguration(createSourceViewerConfiguration());
        setRangeIndicator(new CeylonRangeIndicator());
        configureInsertMode(SMART_INSERT, true);
        setInsertMode(SMART_INSERT);
        problemMarkerManager = new ProblemMarkerManager();
        parseController = new CeylonParseController();
    }
    
    static String[][] getFences() {
        return new String[][] { { "(", ")" }, { "[", "]" }, { "{", "}" } };
    }
    
    public synchronized void pauseBackgroundParsing() {
        backgroundParsingPaused = true;
    }
    
    public synchronized void unpauseBackgroundParsing() {
        backgroundParsingPaused = false;
        if (shouldForceBackgroundParsing) {
            scheduleParsing(true);
        }
    }
    
    public synchronized boolean isBackgroundParsingPaused() {
        return backgroundParsingPaused;
    }
    
    public boolean isInLinkedMode() {
        return linkedMode!=null;
    }
    
    public void setLinkedMode(LinkedModeModel linkedMode,
            Object linkedModeOwner) {
        this.linkedMode = linkedMode;
        this.linkedModeOwner = linkedModeOwner;
    }
    
    public void clearLinkedMode() {
        this.linkedMode = null;
        this.linkedModeOwner = null;
    }
    
    public LinkedModeModel getLinkedMode() {
        return linkedMode;
    }
    
    public Object getLinkedModeOwner() {
        return linkedModeOwner;
    }
    
    public AnnotationCreator getAnnotationCreator() {
        return annotationCreator;
    }
    
    /**
     * Sub-classes may override this method to extend the behavior provided by IMP's
     * standard StructuredSourceViewerConfiguration.
     * @return the StructuredSourceViewerConfiguration to use with this editor
     */
    protected CeylonSourceViewerConfiguration 
            createSourceViewerConfiguration() {
        return new CeylonSourceViewerConfiguration(this);
    }
    
    @Override
    public Object getAdapter(
            @SuppressWarnings("rawtypes") Class required) {
        if (IContentOutlinePage.class.equals(required)) {
            return getOutlinePage();
        }
        if (IToggleBreakpointsTarget.class.equals(required)) {
            return getToggleBreakpointAdapter();
        }
        if (IRunToLineTarget.class.equals(required)) {
            return getToggleBreakpointAdapter();
        }
        return super.getAdapter(required);
    }

    public Object getToggleBreakpointAdapter() {
        if (toggleBreakpointTarget == null) {
            toggleBreakpointTarget = 
                    new ToggleBreakpointAdapter();
        }
        return toggleBreakpointTarget;
    }

    public CeylonOutlinePage getOutlinePage() {
        if (outlinePage == null || outlinePage.isDisposed()) {
            outlinePage = 
                    new CeylonOutlinePage(
                            getParseController(),
                            getCeylonSourceViewer());
            parserScheduler.addModelListener(outlinePage);
         }
         return outlinePage;
    }
    
    @Override
    protected void editorContextMenuAboutToShow(IMenuManager menu) {
        super.editorContextMenuAboutToShow(menu);
        menu.remove(ITextEditorActionConstants.SAVE);
        menu.remove(ITextEditorActionConstants.REVERT);
        menu.remove(ITextEditorActionConstants.SHIFT_LEFT);
        menu.remove(ITextEditorActionConstants.SHIFT_RIGHT);
        menu.remove(ITextEditorActionConstants.QUICK_ASSIST);    
        menu.remove(ITextEditorActionConstants.CUT);
        menu.remove(ITextEditorActionConstants.COPY);
        menu.remove(ITextEditorActionConstants.PASTE);
        menu.remove(ITextEditorActionConstants.UNDO);
    }
    
    @Override
    protected void createActions() {
        super.createActions();

        final ResourceBundle bundle = getBundle(MESSAGE_BUNDLE);
        
        Action action = 
                new ContentAssistAction(bundle, 
                        "ContentAssistProposal.", this);
        action.setActionDefinitionId(CONTENT_ASSIST_PROPOSALS);
        setAction("ContentAssistProposal", action);
        markAsStateDependentAction("ContentAssistProposal", true);

        IVerticalRuler verticalRuler = getVerticalRuler();
        if (verticalRuler!=null) {
            IDocument document = 
                    getDocumentProvider()
                        .getDocument(getEditorInput());
            toggleBreakpointAction = 
                    new ToggleBreakpointAction(this, 
                            document, verticalRuler);
            setAction("ToggleBreakpoint", action);

            enableDisableBreakpointAction= 
                    new RulerEnableDisableBreakpointAction(
                            this, verticalRuler);
            setAction("ToggleBreakpoint", action);
            breakpointPropertiesAction = 
                    new JavaBreakpointPropertiesRulerAction(
                            this, verticalRuler);
        }

//        action= new TextOperationAction(bundle, "Format.", this, 
//                CeylonSourceViewer.FORMAT);
//        action.setActionDefinitionId(FORMAT);
//        setAction("Format", action);
//        markAsStateDependentAction("Format", true);
//        markAsSelectionDependentAction("Format", true);
        //getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.FORMAT_ACTION);

        action= new TextOperationAction(bundle, 
                "AddBlockComment.", this,
                CeylonSourceViewer.ADD_BLOCK_COMMENT);
        action.setActionDefinitionId(ADD_BLOCK_COMMENT);
        setAction(ADD_BLOCK_COMMENT, action); 
        markAsStateDependentAction(ADD_BLOCK_COMMENT, true); 
        markAsSelectionDependentAction(ADD_BLOCK_COMMENT, true); 
        //PlatformUI.getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.ADD_BLOCK_COMMENT_ACTION);

        action = new TextOperationAction(bundle, 
                "RemoveBlockComment.", this,
                CeylonSourceViewer.REMOVE_BLOCK_COMMENT);
        action.setActionDefinitionId(REMOVE_BLOCK_COMMENT);
        setAction(REMOVE_BLOCK_COMMENT, action); 
        markAsStateDependentAction(REMOVE_BLOCK_COMMENT, true); 
        markAsSelectionDependentAction(REMOVE_BLOCK_COMMENT, true); 
        //PlatformUI.getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.REMOVE_BLOCK_COMMENT_ACTION);
        
        action = new TextOperationAction(bundle, 
                "ShowOutline.", this, 
                CeylonSourceViewer.SHOW_OUTLINE, 
                true /* runsOnReadOnly */);
        action.setActionDefinitionId(SHOW_OUTLINE);
        setAction(SHOW_OUTLINE, action);
        //getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.SHOW_OUTLINE_ACTION);

        action = new TextOperationAction(bundle, 
                "ToggleComment.", this, 
                CeylonSourceViewer.TOGGLE_COMMENT);
        action.setActionDefinitionId(TOGGLE_COMMENT);
        setAction(TOGGLE_COMMENT, action);
        //getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.TOGGLE_COMMENT_ACTION);

        action = new TextOperationAction(bundle, 
                "CorrectIndentation.", this, 
                CeylonSourceViewer.CORRECT_INDENTATION);
        action.setActionDefinitionId(CORRECT_INDENTATION);
        setAction(CORRECT_INDENTATION, action);

        action = new GotoMatchingFenceAction(this);
        action.setActionDefinitionId(GOTO_MATCHING_FENCE);
        setAction(GOTO_MATCHING_FENCE, action);
        
        action = new SelectEnclosingAction(this);
        action.setActionDefinitionId(SELECT_ENCLOSING);
        setAction(SELECT_ENCLOSING, action);

        action = new RestorePreviousSelectionAction(this);
        action.setActionDefinitionId(RESTORE_PREVIOUS);
        setAction(RESTORE_PREVIOUS, action);

        action = new TextOperationAction(bundle, 
                "ShowHierarchy.", this, 
                CeylonSourceViewer.SHOW_HIERARCHY, 
                true);
        action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_HIERARCHY);
        setAction(EditorActionIds.SHOW_CEYLON_HIERARCHY, action);

        action = new TextOperationAction(bundle, 
                "ShowInHierarchyView.", this, 
                CeylonSourceViewer.SHOW_IN_HIERARCHY_VIEW, 
                true);
        action.setActionDefinitionId(EditorActionIds.SHOW_IN_CEYLON_HIERARCHY_VIEW);
        setAction(EditorActionIds.SHOW_IN_CEYLON_HIERARCHY_VIEW, action);

        action = new TextOperationAction(bundle, 
                "ShowReferences.", this, 
                CeylonSourceViewer.SHOW_REFERENCES, 
                true);
        action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_REFERENCES);
        setAction(EditorActionIds.SHOW_CEYLON_REFERENCES, action);
        
        action = new TextOperationAction(bundle, 
                "ShowCode.", this, 
                CeylonSourceViewer.SHOW_DEFINITION, 
                true);
        action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_CODE);
        setAction(EditorActionIds.SHOW_CEYLON_CODE, action);
        
        action = editorJ2C().newEclipseTerminateStatementAction(this);
        action.setActionDefinitionId(EditorActionIds.TERMINATE_STATEMENT);
        setAction(EditorActionIds.TERMINATE_STATEMENT, action);

        action = new FormatBlockAction(this);
        action.setActionDefinitionId(EditorActionIds.FORMAT_BLOCK);
        setAction(EditorActionIds.FORMAT_BLOCK, action);

        action = new FormatAction(this);
        action.setActionDefinitionId(EditorActionIds.FORMAT);
        setAction(EditorActionIds.FORMAT, action);

        foldingActionGroup = 
                new FoldingActionGroup(this, getSourceViewer());
        
//        getAction(ITextEditorActionConstants.SHIFT_LEFT)
//            .setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
//                    .getDescriptor(CeylonResources.SHIFT_LEFT));
//        getAction(ITextEditorActionConstants.SHIFT_RIGHT)
//            .setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
//                .getDescriptor(CeylonResources.SHIFT_RIGHT));
        
//        IAction qaa=getAction(ITextEditorActionConstants.QUICK_ASSIST);
//        qaa.setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
//                .getDescriptor(CeylonResources.QUICK_ASSIST));
//        qaa.setText("Quick Fix/Assist");
        
        installQuickAccessAction();
        
    }
    
    @Override
    protected String[] collectContextMenuPreferencePages() {
        String[] pages = super.collectContextMenuPreferencePages();
        String[] result = new String[pages.length+7];
        System.arraycopy(pages, 0, result, 7, pages.length);
        result[0] = CeylonEditorPreferencePage.ID;
        result[1] = CeylonCompletionPreferencePage.ID;
        result[2] = CeylonRefactoringPreferencePage.ID;
        result[3] = CeylonSaveActionsPreferencePage.ID;
        result[4] = CeylonOutlinesPreferencePage.ID;
        result[5] = CeylonOpenDialogsPreferencePage.ID;
        result[6] = CeylonFiltersPreferencePage.ID;
        return result;
    }
    
    @Override
    protected void createNavigationActions() {
        super.createNavigationActions();
        
        StyledText textWidget = 
                getSourceViewer().getTextWidget();
        
        /*IAction action= new SmartLineStartAction(textWidget, false);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_START);
        editor.setAction(ITextEditorActionDefinitionIds.LINE_START, action);

        action= new SmartLineStartAction(textWidget, true);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_LINE_START);
        editor.setAction(ITextEditorActionDefinitionIds.SELECT_LINE_START, action);*/
        
        IAction action = new NavigatePreviousSubWordAction();
        action.setActionDefinitionId(WORD_PREVIOUS);
        setAction(WORD_PREVIOUS, action);
        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL);
        
        action = new NavigateNextSubWordAction();
        action.setActionDefinitionId(WORD_NEXT);
        setAction(WORD_NEXT, action);
        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL);
        
        action = new SelectPreviousSubWordAction();
        action.setActionDefinitionId(SELECT_WORD_PREVIOUS);
        setAction(SELECT_WORD_PREVIOUS, action);
        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL);
        
        action = new SelectNextSubWordAction();
        action.setActionDefinitionId(SELECT_WORD_NEXT);
        setAction(SELECT_WORD_NEXT, action);
        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL);
        
        action = new DeletePreviousSubWordAction();
        action.setActionDefinitionId(DELETE_PREVIOUS_WORD);
        setAction(DELETE_PREVIOUS_WORD, action);
        textWidget.setKeyBinding(SWT.CTRL | SWT.BS, SWT.NULL);
        markAsStateDependentAction(DELETE_PREVIOUS_WORD, true);
        
        action = new DeleteNextSubWordAction();
        action.setActionDefinitionId(DELETE_NEXT_WORD);
        setAction(DELETE_NEXT_WORD, action);
        textWidget.setKeyBinding(SWT.CTRL | SWT.DEL, SWT.NULL);
        markAsStateDependentAction(DELETE_NEXT_WORD, true);
    }
    
    /**
     * Text navigation action to navigate to the next sub-word.
     *
     * @since 3.0
     */
    protected abstract class NextSubWordAction 
            extends TextNavigationAction {

        protected CeylonWordIterator fIterator = 
                new CeylonWordIterator();

        /**
         * Creates a new next sub-word action.
         *
         * @param code Action code for the default operation. 
         *             Must be an action code from 
         *             @see org.eclipse.swt.custom.ST.
         */
        protected NextSubWordAction(int code) {
            super(getSourceViewer().getTextWidget(), code);
        }

        @Override
        public void run() {
            // Check whether we are in a java code partition and the preference is enabled
            if (!CeylonPlugin.getPreferences()
                    .getBoolean(SUB_WORD_NAVIGATION)) {
                super.run();
                return;
            }

            ISourceViewer viewer = getSourceViewer();
            IDocument document = viewer.getDocument();
            try {
                CharacterIterator iter = 
                        (CharacterIterator) 
                            new DocumentCharacterIterator(
                                    document);
                fIterator.setText(iter);
                int caretOffset = 
                        viewer.getTextWidget()
                            .getCaretOffset();
                int position = 
                        widgetOffset2ModelOffset(viewer, 
                                caretOffset);
                if (position == -1)
                    return;

                int next= findNextPosition(position);
                if (isBlockSelectionModeEnabled() && 
                        document.getLineOfOffset(next) != 
                        document.getLineOfOffset(position)) {
                    super.run(); // may navigate into virtual white space
                }
                else if (next != BreakIterator.DONE) {
                    setCaretPosition(next);
                    getTextWidget().showSelection();
                    fireSelectionChanged();
                }
            }
            catch (BadLocationException x) {
                // ignore
            }
        }

        /**
         * Finds the next position after the given position.
         *
         * @param position the current position
         * @return the next position
         */
        protected int findNextPosition(int position) {
            ISourceViewer viewer= getSourceViewer();
            int widget = -1;
            int next = position;
            while (next != BreakIterator.DONE && 
                    widget == -1) { // XXX: optimize
                next = fIterator.following(next);
                if (next != BreakIterator.DONE) {
                    widget = modelOffset2WidgetOffset(viewer, next);
                }
            }

            IDocument document = viewer.getDocument();
            LinkedModeModel model = 
                    LinkedModeModel.getModel(document, 
                            position);
            if (model != null && 
                    next != BreakIterator.DONE) {
                LinkedPosition pos = 
                        new LinkedPosition(document, 
                                position, 0);
                LinkedPosition linkedPosition = 
                        model.findPosition(pos);
                if (linkedPosition != null) {
                    int linkedPositionEnd = 
                            linkedPosition.getOffset() + 
                            linkedPosition.getLength();
                    if (position != linkedPositionEnd && 
                            linkedPositionEnd < next)
                        next= linkedPositionEnd;
                }
                else {
                    LinkedPosition nxt = 
                            new LinkedPosition(document, 
                                    next, 0);
                    LinkedPosition nextLinkedPosition= 
                            model.findPosition(nxt);
                    if (nextLinkedPosition != null) {
                        int nextLinkedPositionOffset = 
                                nextLinkedPosition.getOffset();
                        if (position != nextLinkedPositionOffset && 
                                nextLinkedPositionOffset < next)
                            next = nextLinkedPositionOffset;
                    }
                }
            }

            return next;
        }

        /**
         * Sets the caret position to the sub-word boundary 
         * given with <code>position</code>.
         *
         * @param position Position where the action should move the caret
         */
        protected abstract void setCaretPosition(int position);
    }

    /**
     * Text navigation action to navigate to the next sub-word.
     *
     * @since 3.0
     */
    protected class NavigateNextSubWordAction 
            extends NextSubWordAction {

        /**
         * Creates a new navigate next sub-word action.
         */
        public NavigateNextSubWordAction() {
            super(ST.WORD_NEXT);
        }

        @Override
        protected void setCaretPosition(final int position) {
            ISourceViewer viewer = getSourceViewer();
            int offset = 
                    modelOffset2WidgetOffset(viewer, 
                            position);
            getTextWidget().setCaretOffset(offset);
        }
    }

    /**
     * Text operation action to delete the next sub-word.
     *
     * @since 3.0
     */
    protected class DeleteNextSubWordAction 
            extends NextSubWordAction implements IUpdate {

        /**
         * Creates a new delete next sub-word action.
         */
        public DeleteNextSubWordAction() {
            super(ST.DELETE_WORD_NEXT);
        }

        @Override
        protected void setCaretPosition(final int position) {
            if (!validateEditorInputState()) {
                return;
            }

            final ISourceViewer viewer = getSourceViewer();
            StyledText text = viewer.getTextWidget();
            Point widgetSelection = text.getSelection();
            int caretOffset = text.getCaretOffset();
            if (isBlockSelectionModeEnabled() && 
                    widgetSelection.y != widgetSelection.x) {
                int offset = 
                        modelOffset2WidgetOffset(viewer, 
                                position);
                if (caretOffset == widgetSelection.x) {
                    text.setSelectionRange(widgetSelection.y, 
                            offset - widgetSelection.y);
                }
                else {
                    text.setSelectionRange(widgetSelection.x, 
                            offset - widgetSelection.x);
                }
                text.invokeAction(ST.DELETE_NEXT);
            }
            else {
                Point selection = viewer.getSelectedRange();
                final int caret, length;
                if (selection.y != 0) {
                    caret = selection.x;
                    length = selection.y;
                }
                else {
                    caret = 
                            widgetOffset2ModelOffset(viewer, 
                                    caretOffset);
                    length = position - caret;
                }

                try {
                    viewer.getDocument()
                        .replace(caret, length, "");
                }
                catch (BadLocationException exception) {
                    // Should not happen
                }
            }
        }

        public void update() {
            setEnabled(isEditorInputModifiable());
        }
    }

    /**
     * Text operation action to select the next sub-word.
     *
     * @since 3.0
     */
    protected class SelectNextSubWordAction 
            extends NextSubWordAction {

        /**
         * Creates a new select next sub-word action.
         */
        public SelectNextSubWordAction() {
            super(ST.SELECT_WORD_NEXT);
        }

        @Override
        protected void setCaretPosition(final int position) {
            ISourceViewer viewer = getSourceViewer();

            StyledText text = viewer.getTextWidget();
            if (text != null && !text.isDisposed()) {

                Point selection = text.getSelection();
                int caret = text.getCaretOffset();
                int offset = 
                        modelOffset2WidgetOffset(viewer, 
                                position);

                if (caret == selection.x) {
                    text.setSelectionRange(selection.y, 
                            offset - selection.y);
                }
                else {
                    text.setSelectionRange(selection.x, 
                            offset - selection.x);
                }
            }
        }
    }

    /**
     * Text navigation action to navigate to the previous sub-word.
     *
     * @since 3.0
     */
    protected abstract class PreviousSubWordAction 
            extends TextNavigationAction {

        protected CeylonWordIterator fIterator = 
                new CeylonWordIterator();

        /**
         * Creates a new previous sub-word action.
         *
         * @param code Action code for the default operation. 
         *             Must be an action code from 
         *             @see org.eclipse.swt.custom.ST.
         */
        protected PreviousSubWordAction(final int code) {
            super(getSourceViewer().getTextWidget(), code);
        }

        @Override
        public void run() {
            // Check whether we are in a java code partition 
            // and the preference is enabled
            if (!CeylonPlugin.getPreferences()
                    .getBoolean(SUB_WORD_NAVIGATION)) {
                super.run();
                return;
            }

            ISourceViewer viewer = getSourceViewer();
            IDocument document = viewer.getDocument();
            try {
                CharacterIterator iter = 
                        (CharacterIterator) 
                            new DocumentCharacterIterator(
                                    document);
                fIterator.setText(iter);
                int caretOffset = 
                        viewer.getTextWidget()
                            .getCaretOffset();
                int position = 
                        widgetOffset2ModelOffset(viewer, 
                                caretOffset);
                if (position == -1) {
                    return;
                }

                int previous = findPreviousPosition(position);
                if (isBlockSelectionModeEnabled() && 
                        document.getLineOfOffset(previous) != 
                        document.getLineOfOffset(position)) {
                    super.run(); // may navigate into virtual white space
                }
                else if (previous != BreakIterator.DONE) {
                    setCaretPosition(previous);
                    getTextWidget().showSelection();
                    fireSelectionChanged();
                }
            }
            catch (BadLocationException x) {
                // ignore - getLineOfOffset failed
            }

        }

        /**
         * Finds the previous position before the given position.
         *
         * @param position the current position
         * @return the previous position
         */
        protected int findPreviousPosition(int position) {
            ISourceViewer viewer = getSourceViewer();
            int widget = -1;
            int previous = position;
            while (previous != BreakIterator.DONE && 
                    widget == -1) { // XXX: optimize
                previous = fIterator.preceding(previous);
                if (previous != BreakIterator.DONE)
                    widget = 
                        modelOffset2WidgetOffset(viewer, 
                                previous);
            }

            IDocument document = viewer.getDocument();
            LinkedModeModel model = 
                    LinkedModeModel.getModel(document, 
                            position);
            if (model != null && 
                    previous != BreakIterator.DONE) {
                LinkedPosition pos = 
                        new LinkedPosition(document, 
                                position, 0);
                LinkedPosition linkedPosition = 
                        model.findPosition(pos);
                if (linkedPosition != null) {
                    int linkedPositionOffset = 
                            linkedPosition.getOffset();
                    if (position != linkedPositionOffset && 
                            previous < linkedPositionOffset)
                        previous= linkedPositionOffset;
                }
                else {
                    LinkedPosition prev = 
                            new LinkedPosition(document, 
                                    previous, 0);
                    LinkedPosition previousLinkedPosition = 
                            model.findPosition(prev);
                    if (previousLinkedPosition != null) {
                        int previousLinkedPositionEnd = 
                                previousLinkedPosition.getOffset() + 
                                previousLinkedPosition.getLength();
                        if (position != previousLinkedPositionEnd && 
                                previous < previousLinkedPositionEnd)
                            previous= previousLinkedPositionEnd;
                    }
                }
            }

            return previous;
        }

        /**
         * Sets the caret position to the sub-word boundary 
         * given with <code>position</code>.
         *
         * @param position Position where the action should 
         *                 move the caret
         */
        protected abstract void setCaretPosition(int position);
    }

    /**
     * Text navigation action to navigate to the previous sub-word.
     *
     * @since 3.0
     */
    protected class NavigatePreviousSubWordAction 
            extends PreviousSubWordAction {

        /**
         * Creates a new navigate previous sub-word action.
         */
        public NavigatePreviousSubWordAction() {
            super(ST.WORD_PREVIOUS);
        }

        @Override
        protected void setCaretPosition(final int position) {
            ISourceViewer viewer = getSourceViewer();
            int offset = 
                    modelOffset2WidgetOffset(viewer, 
                            position);
            getTextWidget().setCaretOffset(offset);
        }
    }

    /**
     * Text operation action to delete the previous sub-word.
     *
     * @since 3.0
     */
    protected class DeletePreviousSubWordAction 
            extends PreviousSubWordAction implements IUpdate {

        /**
         * Creates a new delete previous sub-word action.
         */
        public DeletePreviousSubWordAction() {
            super(ST.DELETE_WORD_PREVIOUS);
        }

        @Override
        protected void setCaretPosition(int position) {
            if (!validateEditorInputState()) {
                return;
            }
            final int length;
            ISourceViewer viewer = getSourceViewer();
            StyledText text = viewer.getTextWidget();
            Point widgetSelection = text.getSelection();
            if (isBlockSelectionModeEnabled() && 
                    widgetSelection.y != widgetSelection.x) {
                int caret = text.getCaretOffset();
                int offset = 
                        modelOffset2WidgetOffset(viewer, 
                                position);

                if (caret == widgetSelection.x) {
                    text.setSelectionRange(widgetSelection.y, 
                            offset - widgetSelection.y);
                }
                else {
                    text.setSelectionRange(widgetSelection.x, 
                            offset - widgetSelection.x);
                }
                text.invokeAction(ST.DELETE_PREVIOUS);
            }
            else {
                Point selection = viewer.getSelectedRange();
                if (selection.y != 0) {
                    position= selection.x;
                    length= selection.y;
                }
                else {
                    length = widgetOffset2ModelOffset(viewer, 
                            text.getCaretOffset()) - position;
                }

                try {
                    viewer.getDocument()
                        .replace(position, length, "");
                }
                catch (BadLocationException exception) {
                    // Should not happen
                }
            }
        }

        public void update() {
            setEnabled(isEditorInputModifiable());
        }
    }

    /**
     * Text operation action to select the previous sub-word.
     *
     * @since 3.0
     */
    protected class SelectPreviousSubWordAction 
            extends PreviousSubWordAction {

        /**
         * Creates a new select previous sub-word action.
         */
        public SelectPreviousSubWordAction() {
            super(ST.SELECT_WORD_PREVIOUS);
        }

        @Override
        protected void setCaretPosition(final int position) {
            final ISourceViewer viewer = getSourceViewer();

            final StyledText text = viewer.getTextWidget();
            if (text != null && !text.isDisposed()) {

                Point selection = text.getSelection();
                int caret = text.getCaretOffset();
                int offset = 
                        modelOffset2WidgetOffset(viewer, 
                                position);

                if (caret == selection.x) {
                    text.setSelectionRange(selection.y, 
                            offset - selection.y);
                }
                else {
                    text.setSelectionRange(selection.x, 
                            offset - selection.x);
                }
            }
        }
    }

    protected void initializeKeyBindingScopes() {
        setKeyBindingScopes(new String[] { 
                PLUGIN_ID + ".context", 
                PLUGIN_ID + ".wizardContext" });
    }

    private IHandlerActivation fSourceQuickAccessHandlerActivation;
    private IHandlerActivation fFindQuickAccessHandlerActivation;
    private IHandlerActivation fRefactorQuickAccessHandlerActivation;
    private IHandlerActivation fNavigateQuickAccessHandlerActivation;
    private IHandlerService fHandlerService;

    public static final String REFACTOR_MENU_ID = 
            CeylonPlugin.PLUGIN_ID + ".menu.refactorQuickMenu";
    public static final String NAVIGATE_MENU_ID = 
            CeylonPlugin.PLUGIN_ID + ".menu.navigateQuickMenu";
    public static final String FIND_MENU_ID = 
            CeylonPlugin.PLUGIN_ID + ".menu.findQuickMenu";
    public static final String SOURCE_MENU_ID = 
            CeylonPlugin.PLUGIN_ID + ".menu.sourceQuickMenu";
    
    private class NavigateQuickAccessAction 
            extends QuickMenuAction {
        public NavigateQuickAccessAction() {
            super(NAVIGATE_MENU_ID);
        }
        protected void fillMenu(IMenuManager menu) {
            IContributionItem[] cis = 
                    new NavigateMenuItems()
                        .getContributionItems();
            for (IContributionItem ci: cis) {
                menu.add(ci);
            }
        }
    }
    
    private class RefactorQuickAccessAction 
            extends QuickMenuAction {
        public RefactorQuickAccessAction() {
            super(REFACTOR_MENU_ID);
        }
        protected void fillMenu(IMenuManager menu) {
            IContributionItem[] cis = 
                    new RefactorMenuItems()
                        .getContributionItems();
            for (IContributionItem ci: cis) {
                menu.add(ci);
            }
        }
    }
    
    private class FindQuickAccessAction 
            extends QuickMenuAction {
        public FindQuickAccessAction() {
            super(FIND_MENU_ID);
        }
        protected void fillMenu(IMenuManager menu) {
            IContributionItem[] cis = 
                    new FindMenuItems()
                        .getContributionItems();
            for (IContributionItem ci: cis) {
                menu.add(ci);
            }
        }
    }
    
    private class SourceQuickAccessAction 
            extends QuickMenuAction {
        public SourceQuickAccessAction() {
            super(SOURCE_MENU_ID);
        }
        protected void fillMenu(IMenuManager menu) {
            IContributionItem[] cis = 
                    new SourceMenuItems()
                        .getContributionItems();
            for (IContributionItem ci: cis) {
                menu.add(ci);
            }
        }
    }
    
    private void installQuickAccessAction() {
        fHandlerService = (IHandlerService) 
                getSite().getService(IHandlerService.class);
        if (fHandlerService != null) {
            QuickMenuAction navigateQuickAccessAction = 
                    new NavigateQuickAccessAction();
            fNavigateQuickAccessHandlerActivation = 
                    fHandlerService.activateHandler(
                            navigateQuickAccessAction.getActionDefinitionId(), 
                            new ActionHandler(navigateQuickAccessAction));
            QuickMenuAction refactorQuickAccessAction = 
                    new RefactorQuickAccessAction();
            fRefactorQuickAccessHandlerActivation = 
                    fHandlerService.activateHandler(
                            refactorQuickAccessAction.getActionDefinitionId(), 
                            new ActionHandler(refactorQuickAccessAction));
            QuickMenuAction findQuickAccessAction = 
                    new FindQuickAccessAction();
            fFindQuickAccessHandlerActivation = 
                    fHandlerService.activateHandler(
                            findQuickAccessAction.getActionDefinitionId(), 
                            new ActionHandler(findQuickAccessAction));
            QuickMenuAction sourceQuickAccessAction = 
                    new SourceQuickAccessAction();
            fSourceQuickAccessHandlerActivation = 
                    fHandlerService.activateHandler(
                            sourceQuickAccessAction.getActionDefinitionId(), 
                            new ActionHandler(sourceQuickAccessAction));
        }
    }
    
    protected void uninstallQuickAccessAction() {
        if (fHandlerService != null) {
            fHandlerService.deactivateHandler(
                    fNavigateQuickAccessHandlerActivation); 
            fHandlerService.deactivateHandler(
                    fRefactorQuickAccessHandlerActivation); 
            fHandlerService.deactivateHandler(
                    fFindQuickAccessHandlerActivation); 
            fHandlerService.deactivateHandler(
                    fSourceQuickAccessHandlerActivation); 
        }
    }

    protected boolean isOverviewRulerVisible() {
        return true;
    }

    protected void rulerContextMenuAboutToShow(IMenuManager menu) {
        addDebugActions(menu);
        super.rulerContextMenuAboutToShow(menu);
        menu.appendToGroup(GROUP_RULERS, new Separator());        
        menu.appendToGroup(GROUP_RULERS, 
                getAction("FoldingToggle"));
        menu.appendToGroup(GROUP_RULERS, 
                getAction("FoldingExpandAll"));
        menu.appendToGroup(GROUP_RULERS, 
                getAction("FoldingCollapseAll"));
        menu.appendToGroup(GROUP_RULERS, 
                getAction("FoldingCollapseImports"));
        menu.appendToGroup(GROUP_RULERS, 
                getAction("FoldingCollapseComments"));
    }

    private void addDebugActions(IMenuManager menu) {
        menu.add(toggleBreakpointAction);
        menu.add(enableDisableBreakpointAction);
        menu.add(breakpointPropertiesAction);
    }

    /**
     * Sets the given message as error message to this 
     * editor's status line.
     *
     * @param msg message to be set
     */
    protected void setStatusLineErrorMessage(String msg) {
        IEditorStatusLine statusLine = 
                (IEditorStatusLine) 
                    getAdapter(IEditorStatusLine.class);
        if (statusLine != null)
            statusLine.setMessage(true, msg, null);
    }

    /**
     * Sets the given message as message to this 
     * editor's status line.
     *
     * @param msg message to be set
     * @since 3.0
     */
    protected void setStatusLineMessage(String msg) {
        IEditorStatusLine statusLine = 
                (IEditorStatusLine) 
                    getAdapter(IEditorStatusLine.class);
        if (statusLine != null)
            statusLine.setMessage(false, msg, null);
    }

    public ProblemMarkerManager getProblemMarkerManager() {
        return problemMarkerManager;
    }
    
    @Override
    protected void setTitleImage(Image titleImage) {
        super.setTitleImage(titleImage);
    }

    public IDocumentProvider getDocumentProvider() {
        if (isSrcArchive(getEditorInput())) {
            //Note: I would prefer to register the
            //document provider in plugin.xml but
            //I don't know how to uniquely identity
            //that a IURIEditorInput is a source
            //archive there
            if (sourceArchiveDocumentProvider==null) {
                sourceArchiveDocumentProvider = 
                        new SourceArchiveDocumentProvider();
            }
            return sourceArchiveDocumentProvider;
        }
        else {
            return super.getDocumentProvider();
        }
    }
    
    public CeylonSourceViewer getCeylonSourceViewer() {
        return (CeylonSourceViewer) super.getSourceViewer();
    }

    public void createPartControl(Composite parent) {
        
        // Initialize the parse controller first, since the 
        // initialization of other things (like the context 
        // help support) might depend on it.
        initializeParseController();
        
        super.createPartControl(parent);

        initiateServiceControllers();

        updateTitleImage();
        //setSourceFontFromPreference();
        
        /*((IContextService) getSite().getService(IContextService.class))
                .activateContext(PLUGIN_ID + ".context");*/
                
        IThemeManager themeManager = 
                getWorkbench().getThemeManager();
        CeylonPlugin.log(Status.WARNING, 
                "theme " + themeManager.getCurrentTheme().getId());
        themeManager.addPropertyChangeListener(colorChangeListener);
        updateFontAndCaret();
        themeManager.addPropertyChangeListener(fontChangeListener);
    }

    private boolean shouldForceBackgroundParsing = false;
    
    public synchronized void scheduleParsing(boolean force) {
        if (parserScheduler!=null && !backgroundParsingPaused) {
            parserScheduler.cancel();
            if (force) parseController.dirty();
            parserScheduler.schedule(REPARSE_SCHEDULE_DELAY);
        }
        else {
            if (backgroundParsingPaused) {
                shouldForceBackgroundParsing = true;
            }
            if (force) parseController.dirty();
        }
    }

    private void initializeParseController() {
        IEditorInput editorInput = getEditorInput();
        IFile file = getFile(editorInput);
        IPath filePath = getPath(editorInput);
        IProject project = 
                file!=null && file.exists() ? 
                        file.getProject() : null;
        parseController.initialize(filePath, 
                project, annotationCreator);
    }
    
    private IProblemChangedListener editorIconUpdater = 
            new IProblemChangedListener() {
        @Override
        public void problemsChanged(
                IResource[] changedResources, 
                boolean isMarkerChange) {
            if (isMarkerChange) {
                IEditorInput input= getEditorInput();
                if (input instanceof IFileEditorInput) { // The editor might be looking at something outside the workspace (e.g. system include files).
                    IFileEditorInput fileInput = 
                            (IFileEditorInput) input;
                    IFile file = fileInput.getFile();
                    if (file != null) {
                        for (int i=0; 
                                i<changedResources.length; 
                                i++) {
                            if (changedResources[i].equals(file)) {
                                Shell shell = 
                                        getEditorSite().getShell();
                                if (shell!=null && 
                                        !shell.isDisposed()) {
                                    shell.getDisplay()
                                        .syncExec(new Runnable() {
                                        @Override
                                        public void run() {
                                            updateTitleImage();
                                        }
                                    });
                                }
                            }
                        }
                    }
                }
            }
        }
    };
    
    private IDocumentListener documentListener = 
            new IDocumentListener() {
        public void documentAboutToBeChanged(DocumentEvent event) {
            if (parseController!=null) {
                parseController.resetStage();
            }
        }
        public void documentChanged(DocumentEvent event) {
            synchronized (CeylonEditor.this) {
            	scheduleParsing(true);
            }
        }
    };
    
    private IResourceChangeListener buildListener = 
            new IResourceChangeListener() {
        public void resourceChanged(IResourceChangeEvent event) {
            if (event.getBuildKind()!=CLEAN_BUILD) {
                scheduleParsing(true);
            }
        }
    };

    /**
     * The following listener is intended to detect when the 
     * document associated with this editor changes its 
     * identity, which happens when, e.g., the underlying 
     * resource gets moved or renamed. We need to see when 
     * the editor input changes, so we can watch the new 
     * document.
     */
    private IPropertyListener editorInputPropertyListener = 
            new IPropertyListener() {
        public void propertyChanged(Object source, int propId) {
            if (source == CeylonEditor.this && 
                    propId == IEditorPart.PROP_INPUT) {
                IDocument oldDoc =
                        getParseController()
                            .getDocument();
                IDocument curDoc = 
                        getDocumentProvider()
                            .getDocument(getEditorInput()); 
                if (curDoc!=oldDoc) {
                    // Need to unwatch the old document 
                    // and watch the new document
                    if (oldDoc!=null) {
                        oldDoc.removeDocumentListener(
                                documentListener);
                    }
                    curDoc.addDocumentListener(
                            documentListener);
                }
                initializeParseController();
                scheduleParsing(true);
            }
        }
    };
    
    private IResourceChangeListener moveListener = 
            new IResourceChangeListener() {
        public void resourceChanged(IResourceChangeEvent event) {
            if (parseController==null) {
                return;
            }
            IProject project = parseController.getProject();
            if (project!=null) { //things external to the workspace don't move
                IPath path = parseController.getPath();
                IPath oldWSRelPath = 
                        project.getFullPath().append(path);
                IResourceDelta rd = 
                        event.getDelta()
                            .findMember(oldWSRelPath);
                if (rd != null) {
                    if ((rd.getFlags() & IResourceDelta.MOVED_TO) != 0) {
                        // The net effect of the following is to re-initialize() the parse controller with the new path
                        IPath newPath = rd.getMovedToPath();
                        IPath newProjRelPath = 
                                newPath.removeFirstSegments(1);
                        String newProjName = 
                                newPath.segment(0);
                        IProject proj = 
                                project.getName()
                                    .equals(newProjName) ? 
                                        project : 
                                        project.getWorkspace()
                                            .getRoot()
                                            .getProject(newProjName);
                        // Tell the parse controller about the move - it caches the path
                        // parserScheduler.cancel(); // avoid a race condition if ParserScheduler was starting/in the middle of a run
                        parseController.initialize(
                                newProjRelPath, proj, 
                                annotationCreator);
                    }
                }
            }
        }
    };
    
    private IProblemChangedListener annotationUpdater = 
            new IProblemChangedListener() {
        public void problemsChanged(
                IResource[] changedResources, 
                boolean isMarkerChange) {
            // Remove annotations that were resolved by 
            // changes to other resources.
            // TODO: It would be better to match the markers 
            // to the annotations, and decide exactly which 
            // annotations to remove.
            scheduleParsing(true);
        }
    };
    
    private void initiateServiceControllers() {

        problemMarkerManager.addListener(annotationUpdater);            
        problemMarkerManager.addListener(editorIconUpdater);
        
        parserScheduler = 
                new CeylonParserScheduler(parseController, this);
        
        addModelListener(annotationCreator);        
        addModelListener(refinementAnnotationCreator);
        
        installProjectionSupport();
        
        updateProjectionAnnotationManager();
        
        if (isEditable()) {
            addModelListener(markerAnnotationUpdater);
        }
        
        getSelectionProvider()
            .addPostSelectionChangedListener(rangeAnnotationCreator);
        
        IDocument document = getSourceViewer().getDocument();
        if (document!=null) {
            document.addDocumentListener(documentListener);
        }
        addPropertyListener(editorInputPropertyListener);
        getWorkspace()
            .addResourceChangeListener(moveListener, 
                    IResourceChangeEvent.POST_CHANGE);
        getWorkspace()
            .addResourceChangeListener(buildListener, 
                    IResourceChangeEvent.POST_BUILD);
        modelJ2C().ceylonModel().addModelListener(this);
        
        parserScheduler.schedule();
        
    }
    
    @Override
    public IPostSelectionProvider getSelectionProvider() {
        return (IPostSelectionProvider) 
                super.getSelectionProvider();
    }
    
    private void installProjectionSupport() {
        CeylonSourceViewer sourceViewer = 
                getCeylonSourceViewer();
        
        projectionSupport = 
                new ProjectionSupport(sourceViewer, 
                        getAnnotationAccess(), 
                        getSharedColors());
        MarkerAnnotationPreferences markerAnnotationPreferences = 
                (MarkerAnnotationPreferences) 
                    getAdapter(MarkerAnnotationPreferences.class);
        if (markerAnnotationPreferences != null) {
            List<AnnotationPreference> annPrefs = 
                    markerAnnotationPreferences.getAnnotationPreferences();
            for (Iterator<AnnotationPreference> e 
                    = annPrefs.iterator(); e.hasNext();) {
                Object annotationType = 
                        e.next().getAnnotationType();
                if (annotationType instanceof String) {
                    projectionSupport.addSummarizableAnnotationType(
                            (String) annotationType);
                }
            }
        } 
        /*else {
            projectionSupport.addSummarizableAnnotationType(PARSE_ANNOTATION_TYPE_ERROR);
            projectionSupport.addSummarizableAnnotationType(PARSE_ANNOTATION_TYPE_WARNING);
            projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error");
            projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning");
        }*/
        projectionSupport.install();

        IPreferenceStore store = CeylonPlugin.getPreferences();
        store.setDefault(EDITOR_FOLDING_ENABLED, true);
        if (store.getBoolean(EDITOR_FOLDING_ENABLED)) {
            sourceViewer.doOperation(ProjectionViewer.TOGGLE);
        }
        
        sourceViewer.addProjectionListener(
                projectionAnnotationManager);
    }
    
    private void updateProjectionAnnotationManager() {
        CeylonSourceViewer sourceViewer = 
                getCeylonSourceViewer();
        if (sourceViewer!=null) {
            if (sourceViewer.isProjectionMode()) {
                addModelListener(
                        projectionAnnotationManager);
            }
            else if (projectionAnnotationManager!=null) {
                removeModelListener(
                        projectionAnnotationManager);
            }
        }
    }
    
    @Override
    protected void handlePreferenceStoreChanged(
            PropertyChangeEvent event) {
        super.handlePreferenceStoreChanged(event);
        if (EDITOR_FOLDING_ENABLED.equals(event.getProperty())) {
            updateProjectionAnnotationManager();
            new ToggleFoldingRunner(this)
                .runWhenNextVisible();
        }
        CeylonSourceViewer sourceViewer = getCeylonSourceViewer();
        if (sourceViewer!=null) {
        	ContentAssistant contentAssistant = 
        			sourceViewer.getContentAssistant();
        	configCompletionPopup(contentAssistant);
        }
    }
    
    private IPropertyChangeListener propertyChangeListener;
    
    @Override
    protected void initializeEditor() {
        propertyChangeListener = 
                new IPropertyChangeListener() {
            @Override
            public void propertyChange(
                    PropertyChangeEvent event) {
                handlePreferenceStoreChanged(event);
            }
        };
        CeylonPlugin.getPreferences()
            .addPropertyChangeListener(
                    propertyChangeListener);
        super.initializeEditor();
    }
    
    public void updateTitleImage() {
        IFile file = getFile(getEditorInput());
        if (file!=null) {
            setTitleImage(getImageForFile(file));
        }
    }
    
    public void dispose() {
        
        if (editorIconUpdater!=null) {
            problemMarkerManager.removeListener(
                    editorIconUpdater);
            editorIconUpdater = null;
        }
        if (annotationUpdater!=null) {
            problemMarkerManager.removeListener(
                    annotationUpdater);
            annotationUpdater = null;
        }
        if (propertyChangeListener!=null) {
            CeylonPlugin.getPreferences()
                .removePropertyChangeListener(
                        propertyChangeListener);
            propertyChangeListener = null;
        }
        
        outlinePage = null;
        
        if (buildListener!=null) {
            getWorkspace()
                .removeResourceChangeListener(buildListener);
            buildListener = null;
        }
        if (moveListener!=null) {
            getWorkspace()
                .removeResourceChangeListener(moveListener);
            moveListener = null;
        }
        
        modelJ2C().ceylonModel().removeModelListener(this);
        
        IDocument document = 
                getParseController().getDocument();
        if (document!=null) {
            document.removeDocumentListener(
                    documentListener);
        }
        removePropertyListener(editorInputPropertyListener);
        
        if (toggleBreakpointAction!=null) {
            toggleBreakpointAction.dispose(); // this holds onto the IDocument
        }
        if (foldingActionGroup!=null) {
            foldingActionGroup.dispose();
        }
        
        if (projectionSupport!=null) {
            projectionSupport.dispose();
            projectionSupport = null;
        }

        if (parserScheduler!=null) {
            parserScheduler.cancel(); // avoid unnecessary work after the editor is asked to close down
            parserScheduler.dispose();
            parserScheduler = null;
        }
        
        parseController = null;
        
        uninstallQuickAccessAction();

        super.dispose();
        
        /*if (fResourceListener != null) {
            ResourcesPlugin.getWorkspace().removeResourceChangeListener(fResourceListener);
        }*/
        IThemeManager themeManager = 
                getWorkbench().getThemeManager();
        themeManager.removePropertyChangeListener(colorChangeListener);
        themeManager.removePropertyChangeListener(fontChangeListener);
        
    }

    private IPropertyChangeListener colorChangeListener = 
            new IPropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent event) {
            if (Highlights.isColorChange(event)) {
                CeylonPlugin.log(Status.WARNING, 
                        event.getProperty() + "=" +
                        event.getNewValue());
                Highlights.refreshColors();
                getSourceViewer()
                    .invalidateTextPresentation();
            }
        }
    };
    
    IPropertyChangeListener fontChangeListener = 
            new IPropertyChangeListener() {
        @Override
        public void propertyChange(PropertyChangeEvent event) {
            if (event.getProperty()
                    .equals(CeylonPlugin.EDITOR_FONT_PREFERENCE)) {
                updateFontAndCaret();
            }
        }
    };
        
    private void updateFontAndCaret() {
        getSourceViewer()
            .getTextWidget()
            .setFont(CeylonPlugin.getEditorFont());
        try {
            Method updateCaretMethod = 
                    AbstractTextEditor.class
                        .getDeclaredMethod("updateCaret");
            updateCaretMethod.setAccessible(true);
            updateCaretMethod.invoke(this);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    protected final SourceViewer createSourceViewer(
            Composite parent, IVerticalRuler ruler, 
            int styles) {
        
        fAnnotationAccess = getAnnotationAccess();
        fOverviewRuler = 
                createOverviewRuler(getSharedColors());
        
        SourceViewer viewer = 
                new CeylonSourceViewer(this, 
                        parent, ruler, 
                        getOverviewRuler(), 
                        isOverviewRulerVisible(), 
                        styles);
        
        // ensure decoration support has been created and configured.
        getSourceViewerDecorationSupport(viewer);
        
        viewer.getTextWidget()
            .addCaretListener(new CaretListener() {
            @Override
            public void caretMoved(CaretEvent event) {
                Object adapter = 
                        getAdapter(IVerticalRulerInfo.class);
                if (adapter instanceof CompositeRuler) {
                    // redraw initializer annotations according to cursor position
                    CompositeRuler compositeRuler = 
                            (CompositeRuler) adapter;
                    compositeRuler.update();
                }
            }
        });
    
        return viewer;
    }
    
    protected void configureSourceViewerDecorationSupport(
            SourceViewerDecorationSupport support) {
        installBracketMatcher(support);
        super.configureSourceViewerDecorationSupport(support);
    }
    
    //These preferences can't be stored in the CeylonPlugin preferences
    public final static String MATCHING_BRACKET = 
            "matchingBrackets";
    public final static String MATCHING_BRACKETS_COLOR = 
            "matchingBracketsColor";
    public final static String SELECTED_BRACKET = 
            "highlightBracketAtCaretLocation";
    public final static String ENCLOSING_BRACKETS = 
            "enclosingBrackets";
    
    private void installBracketMatcher(
            SourceViewerDecorationSupport support) {
        initializeBrackMatcherPreferences();
        bracketMatcher = new CeylonCharacterPairMatcher();
        support.setCharacterPairMatcher(bracketMatcher);
        support.setMatchingCharacterPainterPreferenceKeys(
                MATCHING_BRACKET, MATCHING_BRACKETS_COLOR, 
                SELECTED_BRACKET, ENCLOSING_BRACKETS);
    }

    private final static String MATCHING_BRACKETS_PREF = 
            PLUGIN_ID + ".theme.matchingBracketsColor";
    
    public static void initializeBrackMatcherPreferences() {
        ITheme currentTheme = getCurrentTheme();
        Color color = 
                currentTheme.getColorRegistry()
                    .get(MATCHING_BRACKETS_PREF);
        String colorString;
        if (color==null) {
            //because I can't trust the ThemeManager :-(
            colorString = "0,120,255";
        }
        else {
            colorString = 
                    color.getRed() +"," + 
                    color.getGreen() + "," + 
                    color.getBlue();
        }
        IPreferenceStore store = 
                //can't be stored in the CeylonPlugin preferences!
                EditorsUI.getPreferenceStore(); 
        store.setDefault(MATCHING_BRACKET, true);
        store.setDefault(ENCLOSING_BRACKETS, false);
        store.setDefault(SELECTED_BRACKET, false);
        store.setDefault(MATCHING_BRACKETS_COLOR, colorString);
        store.setDefault(EDITOR_FOLDING_ENABLED, true);
    }
    
    public ICharacterPairMatcher getBracketMatcher() {
        return bracketMatcher;
    }
    
    private void doSaveInternal(IProgressMonitor progressMonitor) {
        super.doSave(progressMonitor);
    }

    public void saveWithoutActions() {
        doSaveInternal(getProgressMonitor());
    }
    
    @Override
    public void doSave(IProgressMonitor progressMonitor) {
        CeylonSourceViewer viewer = getCeylonSourceViewer();
        IDocument doc = viewer.getDocument();
        IPreferenceStore prefs = CeylonPlugin.getPreferences();
        boolean normalizeWs = 
                prefs.getBoolean(NORMALIZE_WS) &&
                EditorsUI.getPreferenceStore()
                    .getBoolean(EDITOR_SPACES_FOR_TABS);
        boolean normalizeNl = 
                prefs.getBoolean(NORMALIZE_NL);
        boolean stripTrailingWs = 
                prefs.getBoolean(STRIP_TRAILING_WS);
        boolean cleanImports = 
                prefs.getBoolean(CLEAN_IMPORTS);
        boolean format = prefs.getBoolean(FORMAT);
        if (cleanImports) {
            try {
                importsJ2C().cleanImports(
                        parseController, doc);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (format) {
            try {
                FormatAction.format(parseController, 
                        viewer.getDocument(), 
                        EditorUtil.getSelection(this), 
                        false, 
                        getSelectionProvider());
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        else if (normalizeWs || 
                 normalizeNl || 
                 stripTrailingWs) {
            normalize(viewer, doc, 
                    normalizeWs, 
                    normalizeNl, 
                    stripTrailingWs);
        }
        doSaveInternal(progressMonitor);
    }

    private static void normalize(CeylonSourceViewer viewer, 
            IDocument doc,
            boolean normalizeWs, 
            boolean normalizeNl, 
            boolean stripTrailingWs) {
        DocumentRewriteSession rewriteSession = null;
        if (doc instanceof IDocumentExtension4) {
            rewriteSession = 
                    ((IDocumentExtension4) doc)
                        .startRewriteSession(SEQUENTIAL);
        }
        
        Point range = viewer.getSelectedRange();
        int modelOffset = range.x;
        int modelLength = range.y;
        try {
            String text = doc.get();
            String normalized = 
                    normalize(text, doc, 
                            normalizeWs, 
                            normalizeNl, 
                            stripTrailingWs);
            if (!normalized.equals(text)) {
                StyledText widget = viewer.getTextWidget();
                Point selection = widget.getSelectionRange();
                StyledTextContent content = widget.getContent();
                String textBeforeRange = 
                        content.getTextRange(
                                0, selection.x);
                int offset = 
                        normalize(textBeforeRange, doc, 
                                normalizeWs, 
                                normalizeNl, 
                                stripTrailingWs)
                            .length();
                String textInRange = 
                        content.getTextRange(
                                selection.x, selection.y);
                int length = 
                        normalize(textInRange, doc, 
                                normalizeWs, 
                                normalizeNl, 
                                stripTrailingWs)
                            .length();
                modelOffset = 
                        viewer.widgetOffset2ModelOffset(offset);
                modelLength = 
                        viewer.widgetOffset2ModelOffset(offset+length) 
                            - modelOffset;
                new ReplaceEdit(0, text.length(), normalized)
                    .apply(doc);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (doc instanceof IDocumentExtension4) {
                ((IDocumentExtension4) doc)
                    .stopRewriteSession(rewriteSession);
            }
            viewer.setSelectedRange(modelOffset, modelLength);
        }
    }

    private static String normalize(String text, 
            IDocument doc, 
            boolean normalizeWs, 
            boolean normalizeNl, 
            boolean stripTrailingWs) {
        if (stripTrailingWs) {
            text = TRAILING_WS.matcher(text).replaceAll("");
        }
        if (normalizeWs) {
            text = text.replace("\t", utilJ2C().indents().getDefaultIndent());
        }
        if (normalizeNl) {
            String delim = utilJ2C().indents().getDefaultLineDelimiter(doc);
            for (String s: doc.getLegalLineDelimiters()) {
                text = text.replace(s, delim);
            }
        }
        return text;
    }

//    protected void doSetInput(IEditorInput input) throws CoreException {
//        // Catch CoreExceptions here, since it's possible that things like IOExceptions occur
//        // while retrieving the input's contents, e.g., if the given input doesn't exist.
//        try {
//            super.doSetInput(input);
//        } 
//        catch (CoreException e) {
//            if (e.getCause() instanceof IOException) {
//                throw new CoreException(new Status(IStatus.ERROR, CeylonPlugin.PLUGIN_ID, 
//                        0, "Unable to read source text", e.getStatus().getException()));
//            }
//        }
//        setInsertMode(SMART_INSERT);    
//    }

    @Override
    protected void doSetInput(IEditorInput input) 
            throws CoreException {
        
        input = EditorUtil.adjustEditorInput(input);

        if (input != null) {
            //the following crazy stuff seems to be needed in
            //order to get syntax highlighting in structured
            //compare viewer
            CeylonSourceViewer sourceViewer = 
                    getCeylonSourceViewer();
            if (sourceViewer!=null) {
                // uninstall & unregister preference store listener
                getSourceViewerDecorationSupport(sourceViewer)
                    .uninstall();
                sourceViewer.unconfigure();
                //setPreferenceStore(createCombinedPreferenceStore(input));
                // install & register preference store listener
                sourceViewer.configure(getSourceViewerConfiguration());
                getSourceViewerDecorationSupport(sourceViewer)
                    .install(getPreferenceStore());
            }
        }
        
        super.doSetInput(input);
        
        if (input != null) {
            //have to do this or we get a funny-looking caret
            setInsertMode(SMART_INSERT);
        }
    }

    /**
     * Add a Model listener to this editor. Any time the underlying AST is recomputed, the listener is notified.
     * 
     * @param listener the listener to notify of Model changes
     */
    public void addModelListener(TreeLifecycleListener listener) {
        parserScheduler.addModelListener(listener);
    }

    /**
     * Remove a Model listener from this editor.
     * 
     * @param listener the listener to remove
     */
    public void removeModelListener(TreeLifecycleListener listener) {
        parserScheduler.removeModelListener(listener);
    }
    
    public String getSelectionText() {
        IRegion sel = getSelection();
        IDocument document = 
                getDocumentProvider()
                    .getDocument(getEditorInput());
        if (document==null) {
            return "";
        }
        try {
            return document.get(
                    sel.getOffset(), 
                    sel.getLength());
        } 
        catch (BadLocationException e) {
            e.printStackTrace();
            return "";
        }
    }

    public IRegion getSelection() {
        ITextSelection ts = (ITextSelection) 
                getSelectionProvider().getSelection();
        return new Region(ts.getOffset(), ts.getLength());
    }

    /**
     * Returns the signed current selection.
     * The length will be negative if the resulting selection
     * is right-to-left (RtoL).
     * The selection offset is model based.
     */
    public IRegion getSignedSelection() {
        ISourceViewer sourceViewer = getSourceViewer();
        StyledText text = sourceViewer.getTextWidget();
        Point selection = text.getSelectionRange();
        if (text.getCaretOffset() == selection.x) {
            selection.x = selection.x + selection.y;
            selection.y = -selection.y;
        }
        selection.x = 
                widgetOffset2ModelOffset(sourceViewer, 
                        selection.x);
        return new Region(selection.x, selection.y);
    }
    
    public boolean canPerformFind() {
        return true;
    }

    public CeylonParseController getParseController() {
        return parseController;
    }

    public String toString() {
        return "Ceylon Editor for " + 
                getEditorInput().getName();
    }
    
    boolean isFoldingEnabled() {
        return getPreferenceStore()
                .getBoolean(EDITOR_FOLDING_ENABLED);
    }

    public ITextSelection getSelectionFromThread() {
        final class GetSelection implements Runnable {
            ITextSelection selection;
            @Override
            public void run() {
                ISelectionProvider sp = 
                        getSelectionProvider();
                selection = sp==null ? null : 
                    (ITextSelection) sp.getSelection();
            }
            ITextSelection getSelection() {
                Display.getDefault().syncExec(this);
                return selection;
            }
        }
        return new GetSelection().getSelection();
    }
    
    public Node getSelectedNode() {
        CeylonParseController cpc = getParseController();
        if (cpc==null || cpc.getLastCompilationUnit()==null) {
            return null;
        }
        else {
            return findNode(cpc.getLastCompilationUnit(), 
                    cpc.getTokens(), 
                    EditorUtil.getSelection(this));
        }
    }
    
    @Override
    public Object ceylonModelParsed(CeylonProject<IProject, IResource, IFolder, IFile> project) {
        IEditorInput input = getEditorInput();
        if (input instanceof FileStoreEditorInput) {
            FileStoreEditorInput fsei = 
                    (FileStoreEditorInput) input;
            final IEditorInput newInput = 
                    EditorUtil.fixSourceArchiveInput(fsei);
            if (newInput != null) {
                IWorkbenchPartSite site = getSite();
                if (site != null) {
                    site.getPage()
                        .getWorkbenchWindow()
                        .getWorkbench()
                        .getDisplay()
                        .asyncExec(new Runnable() {
                            @Override
                            public void run() {
                                setInput(newInput);
                            }
                        });
                }
            }
        }
        return null;
    }

    @Override
    public Object ceylonProjectAdded(
            CeylonProject<IProject, IResource, IFolder, IFile> project) {
        return null;
    }

    @Override
    public Object ceylonProjectRemoved(
            CeylonProject<IProject, IResource, IFolder, IFile> project) {
        return null;
    }
    
    @Override
    public Object buildMessagesChanged(
            CeylonProject<IProject,IResource,IFolder,IFile> project,
            ceylon.language.Iterable<? extends CeylonProjectBuild<IProject,IResource,IFolder,IFile>.SourceFileMessage,? extends Object> frontendMessages,
            ceylon.language.Iterable<? extends CeylonProjectBuild<IProject,IResource,IFolder,IFile>.SourceFileMessage,? extends Object> backMessages,
            ceylon.language.Iterable<? extends CeylonProjectBuild<IProject,IResource,IFolder,IFile>.ProjectMessage,? extends Object> projectMessages) {
        return null;
    }

    @Override
    public Object modelFilesUpdated(
            Iterable<? extends FileVirtualFile<IProject, IResource, IFolder, IFile>, ? extends Object> arg0) {
        return null;
    }

    @Override
    public Object modelPhasedUnitsTypechecked(
            Iterable<? extends ProjectPhasedUnit<IProject, IResource, IFolder, IFile>, ? extends Object> arg0) {
        return null;
    }

    @Override
    public Object externalPhasedUnitsTypechecked(
            Iterable<? extends ExternalPhasedUnit, ? extends Object> externalPhasedUnits,
            boolean fullyTypechecked) {
        return null;
    }
}
